﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ABB.Common.DataModel;
using System.Globalization;

namespace ABB.Common.BusinessLogic.Reporting
{
    /// <summary>
    /// Evaluiert die übergebenen Report Filter gegen entsprechende Lots.
    /// </summary>
    internal class ReportFilterEvaluator
    {
        private readonly ProjectPlanningDataSet.ReportRow m_Report;
        private readonly IReportDataLoader m_DataLoader;
        private readonly ProjectPlanningDataSet.UserRow m_CurrentUser;

        #region Static Implementation

        /// <summary>
        /// Evaluiert einen Wert mit einem <see cref="ReportFilterConditionOperator"/> und
        /// einem entsprechenden Ausdruck.
        /// </summary>
        /// <param name="lhs">Der Eingabewert, welcher überprüft werden soll (left-hand-side).</param>
        /// <param name="conditionOperator">Der Vergleichsoperator.</param>
        /// <param name="rhs">Der Operatoren-Wert wird für die Auswertung verwendet (right-hand-side).</param>
        /// <returns></returns>
        public static bool EvaluateFilterOperator(string lhs,
            ReportFilterConditionOperator conditionOperator, string rhs)
        {
            if (conditionOperator == ReportFilterConditionOperator.None)
                throw new ArgumentException("condition none can not be evaluated.");
            if (rhs == null)
                throw new ArgumentNullException("rhs");

            // EMPTY ist ein Spezialfall. Es kann auf EMPTY gefiltert werden
            // ist es aber kein Filter auf EMPTY, so sind leere Werte immer zulässig.
            if (string.IsNullOrEmpty(lhs))
            {
                if (rhs.ToUpper() == "EMPTY")
                {
                    if (conditionOperator == ReportFilterConditionOperator.Equals)
                        return string.IsNullOrEmpty(lhs);
                    else
                        return !string.IsNullOrEmpty(lhs);
                }
                else
                    return true;
            }

            // ignore casing
            rhs = rhs.Trim().ToLower();
            lhs = lhs.Trim().ToLower();

            // String Auswertungen
            switch (conditionOperator)
            {
                case ReportFilterConditionOperator.Contains:
                    return lhs.Contains(rhs);
                case ReportFilterConditionOperator.DoesNotEqual:
                    return !lhs.Equals(rhs, StringComparison.InvariantCultureIgnoreCase);
                case ReportFilterConditionOperator.EndsWith:
                    return lhs.EndsWith(rhs, StringComparison.InvariantCultureIgnoreCase);
                case ReportFilterConditionOperator.Equals:
                    return lhs.Equals(rhs, StringComparison.InvariantCultureIgnoreCase);
                case ReportFilterConditionOperator.StartsWith:
                    return lhs.StartsWith(rhs, StringComparison.InvariantCultureIgnoreCase);
            }

            // Versuchen als Datum auszuwerten
            DateTime lhsDate;
            DateTime rhsDate;

            if (DateTime.TryParse(lhs, out lhsDate) &&
                DateTime.TryParse(rhs, out rhsDate))
            {
                // Datumsvergleich kann stattfinden
                switch (conditionOperator)
                {
                    case ReportFilterConditionOperator.IsGreaterThan:
                        return lhsDate > rhsDate;
                    case ReportFilterConditionOperator.IsGreaterThanOrEqualTo:
                        return lhsDate >= rhsDate;
                    case ReportFilterConditionOperator.IsLessThan:
                        return lhsDate < rhsDate;
                    case ReportFilterConditionOperator.IsLessThanOrEqualTo:
                        return lhsDate <= rhsDate;
                }
            }

            // Versuchen als Zahlen auszuwerten
            double lhsNumber;
            double rhsNumber;

            if (double.TryParse(lhs, out lhsNumber) &&
                double.TryParse(rhs, out rhsNumber))
            {
                switch (conditionOperator)
                {
                    case ReportFilterConditionOperator.IsGreaterThan:
                        return lhsNumber > rhsNumber;
                    case ReportFilterConditionOperator.IsGreaterThanOrEqualTo:
                        return lhsNumber >= rhsNumber;
                    case ReportFilterConditionOperator.IsLessThan:
                        return lhsNumber < rhsNumber;
                    case ReportFilterConditionOperator.IsLessThanOrEqualTo:
                        return lhsNumber <= rhsNumber;
                }
            }

            // Ansonsten Filter ignorieren, wenn garnichts passt.
            return true;
        }

        #endregion

        #region C'tors

        /// <summary>
        /// Öffentlicher Konstruktor
        /// </summary>
        public ReportFilterEvaluator(ProjectPlanningDataSet.ReportRow report,
                                     ProjectPlanningDataSet.UserRow currentUser,
                                     IReportDataLoader dataLoader)
        {
            if (report == null) throw new ArgumentNullException("report");
            if (currentUser == null) throw new ArgumentNullException("currentUser");
            if (dataLoader == null) throw new ArgumentNullException("dataLoader");

            this.m_Report = report;
            this.m_DataLoader = dataLoader;
            this.m_CurrentUser = currentUser;
        }

        #endregion

        /// <summary>
        /// Gibt sämtliche Lots zurück, nach Auswertung der Filter für den Report.
        /// </summary>
        /// <returns><see cref="IEnumerable<ProjectPlanningDataSet.LotRow>"/></returns>
        public IEnumerable<ProjectPlanningDataSet.LotRow> GetFilteredLots()
        {
            var filters = this.m_Report.GetReportFilterRows();

            IList<ProjectPlanningDataSet.LotRow> filteredLots = new List<ProjectPlanningDataSet.LotRow>();

            foreach (var lot in this.m_DataLoader.DataSet.Lot)
            {
                if (filters.All(f => (this.EvaluateFilterForLot(lot, f))))
                {
                    if (this.m_Report.IncludeClosedLots ||
                        (lot.IsClosedDateNull() || lot.ClosedDate > DateTime.Today))
                        filteredLots.Add(lot);
                }
            }

            return filteredLots;
        }

        #region Private Implementation

        /// <summary>
        /// Evaluiert einen Filter für einen Lot.
        /// </summary>
        /// <param name="lot"></param>
        /// <param name="filter"></param>
        /// <returns></returns>
        private bool EvaluateFilterForLot(ProjectPlanningDataSet.LotRow lot,
            ProjectPlanningDataSet.ReportFilterRow filter)
        {
            // get the value
            string value = this.m_DataLoader.GetValueForLotByReportColumn(lot,
                                                        filter.ReportColumnRow);

            bool condition1Result = ReportFilterEvaluator.EvaluateFilterOperator(value,
                (ReportFilterConditionOperator)filter.ConditionOperator1,
                    this.ResolveFilterKeywords(filter.ConditionValue1));

            if (filter.ConditionOperator2 == (short)ReportFilterConditionOperator.None)
            {
                if (condition1Result)
                    return true;
            }
            else
            {
                bool condition2Result = ReportFilterEvaluator.EvaluateFilterOperator(value,
                    (ReportFilterConditionOperator)filter.ConditionOperator2,
                    this.ResolveFilterKeywords(filter.ConditionValue2));

                if (filter.ConnectionOperator == (short)ReportFilterConnectionOperator.And)
                {
                    if (condition1Result && condition2Result)
                        return true;
                }
                else if (filter.ConnectionOperator == (short)ReportFilterConnectionOperator.Or)
                {
                    if (condition1Result || condition2Result)
                        return true;
                }
            }

            return false;
        }

        /// <summary>
        /// Löst die Schlüsselwörter eines Filters auf.
        /// </summary>
        /// <param name="filterConditionValue">Der Filter-Bedingung-Wert, welcher aufgelöst werden soll.</param>
        /// <returns>Gibt <paramref name="filterConditionValue"/> zurück, sofern kein Schlüsselwort
        /// zum auflösen enthalten ist oder ansonsten den aufgelösten Wert.</returns>
        private string ResolveFilterKeywords(string filterConditionValue)
        {
            if (string.IsNullOrEmpty(filterConditionValue)) return filterConditionValue;

            switch (filterConditionValue.Trim().ToUpper())
            {
                case "TODAY":
                    return DateTime.Today.ToShortDateString();

                case "CURRENT":
                    return this.m_CurrentUser.Username;

                default:
                    return filterConditionValue;
            }
        }

        #endregion
    }
}
